package com.weilele.mvvm.utils.file

import android.content.ContentValues
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import androidx.annotation.WorkerThread
import com.weilele.mvvm.app
import com.weilele.mvvm.base.helper.ignoreErrorToast
import com.weilele.mvvm.logI
import com.weilele.mvvm.mainHandler
import com.weilele.mvvm.utils.activity.isMainThread
import com.weilele.mvvm.utils.coroutine.launchOnThread
import com.weilele.mvvm.utils.download.ProgressResponseBody
import com.weilele.mvvm.utils.net.OkHttpCertUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive
import okhttp3.Call
import okhttp3.Request
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.lang.Exception
import java.util.concurrent.TimeUnit


enum class SaveFileType(val path: String) {
    Video(app.packageName),
    Music(app.packageName),
    Image(app.packageName),
    Download(app.packageName)
}

/**
 * 保存文件
 */
fun saveFileToPublicDirectory(
        file: File,
        type: SaveFileType,
        mimeType: String,
        displayName: String = file.name,
        deleteOldFile: Boolean = true,
        path: String = type.path,
        setContentValues: Function1<ContentValues, Unit>? = null
) {
    if (file.isDirectory) {
        return
    }
    saveByteArrayToPublicDirectory(
            file.readBytes(),
            type,
            mimeType,
            displayName,
            path,
            setContentValues
    )
    if (deleteOldFile && file.exists()) {
        file.delete()
    }
}

/**
 * 需要读取存储权限
 * 将文件写入公共数据库
 * 将文件保存到公共的媒体文件夹
 * 这里的filepath不是绝对路径，而是某个媒体文件夹下的子路径，和沙盒子文件夹类似
 * 这里的filename单纯的指文件名，不包含路径
 */
fun saveByteArrayToPublicDirectory(
        byte: ByteArray,
        type: SaveFileType,
        mimeType: String,
        displayName: String,
        path: String = type.path,
        setContentValues: Function1<ContentValues, Unit>? = null
) {
    val uri = insetFileToContentResolver(type, mimeType, displayName, path, setContentValues)
    if (uri != null) {
        app.contentResolver.openOutputStream(uri)?.write(byte)
    }
}

/**
 * 文件直接写入到公共目录
 */
fun saveToPublicDirectory(
        scope: CoroutineScope,
        url: String,
        type: SaveFileType,
        mimeType: String,
        displayName: String,
        path: String = type.path,
        setContentValues: Function1<ContentValues, Unit>? = null
): DownloadToOutputStream? {
    return saveToPublicDirectory(
            scope,
            url,
            type,
            mimeType,
            displayName,
            path,
            setContentValues,
            null
    )
}

fun saveToPublicDirectory(
        scope: CoroutineScope,
        url: String,
        type: SaveFileType,
        mimeType: String,
        displayName: String,
        path: String = type.path,
        setContentValues: Function1<ContentValues, Unit>? = null,
        uriCall: Function1<Uri, Unit>?
): DownloadToOutputStream? {
    val uri = insetFileToContentResolver(type, mimeType, displayName, path, setContentValues)
            ?: return null
    //若生成了uri，则表示该文件添加成功
    //使用流将内容写入该uri中即可
    val output = app.contentResolver.openOutputStream(uri) ?: return null
    uriCall?.invoke(uri)
    return DownloadToOutputStream(scope, url, output)
}

fun deleteFromContentResolver(uri: Uri) {
    app.contentResolver.delete(uri, null, null)
}

/**
 * 直接将字节数组写入，适合小文件
 */
fun insetFileToContentResolver(
        type: SaveFileType,
        mimeType: String,
        displayName: String,
        path: String = type.path,
        setContentValues: Function1<ContentValues, Unit>? = null
): Uri? {
    return ignoreErrorToast {
        //设置保存参数到ContentValues中
        val contentValues = ContentValues()
        //执行insert操作，向系统文件夹中添加文件
        //EXTERNAL_CONTENT_URI代表外部存储器，该值不变
        val contentResolver = app.contentResolver
        val saveUri = when (type) {
            SaveFileType.Video -> {
                //设置文件名
                contentValues.put(MediaStore.Video.Media.DISPLAY_NAME, displayName)
                contentValues.put(MediaStore.Video.Media.TITLE, displayName)
                //设置文件类型
                contentValues.put(MediaStore.Video.Media.MIME_TYPE, "video/${mimeType}")
                //兼容Android Q和以下版本
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    //android Q中不再使用DATA字段，而用RELATIVE_PATH代替
                    //RELATIVE_PATH是相对路径不是绝对路径
                    //DCIM是系统文件夹，关于系统文件夹可以到系统自带的文件管理器中查看，不可以写没存在的名字
                    contentValues.put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/${path}");
                } else {
                    contentValues.put(
                            MediaStore.Video.Media.DATA,
                            getOldSdkPath(displayName, path, "Movies")
                    )
                }
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI
            }
            SaveFileType.Image -> {
                contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, displayName)
                contentValues.put(MediaStore.Images.Media.TITLE, displayName)
                contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/${mimeType}")
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/${path}");
                } else {
                    contentValues.put(
                            MediaStore.Images.Media.DATA,
                            getOldSdkPath(displayName, path, "Pictures")
                    )
                }
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI
            }
            SaveFileType.Music -> {
                contentValues.put(MediaStore.Audio.Media.DISPLAY_NAME, displayName)
                contentValues.put(MediaStore.Audio.Media.TITLE, displayName)
                contentValues.put(MediaStore.Audio.Media.MIME_TYPE, "audio/${mimeType}")
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    contentValues.put(MediaStore.Audio.Media.RELATIVE_PATH, "Music/${path}");
                } else {
                    contentValues.put(
                            MediaStore.Audio.Media.DATA,
                            getOldSdkPath(displayName, path, "Music")
                    )
                }
                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
            }
            SaveFileType.Download -> {
                contentValues.put(MediaStore.Downloads.DISPLAY_NAME, displayName)
                contentValues.put(MediaStore.Downloads.TITLE, displayName)
                contentValues.put(MediaStore.Downloads.MIME_TYPE, mimeType)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    contentValues.put(MediaStore.Downloads.RELATIVE_PATH, "Download/${path}")
                    MediaStore.Downloads.EXTERNAL_CONTENT_URI
                } else {
                    contentValues.put(
                            MediaStore.Files.FileColumns.DATA,
                            getOldSdkPath(displayName, path, "Download")
                    )
                    MediaStore.Files.getContentUri("external")
                }
            }
        }
        setContentValues?.invoke(contentValues)
        return contentResolver.insert(saveUri, contentValues)
    }
}

private fun getOldSdkPath(
        displayName: String,
        path: String,
        type: String,
): String {
    val oldSdkPathDir =
            "${Environment.getExternalStorageDirectory().absolutePath + "/$type/"}${path}/"
    val dir = File(oldSdkPathDir)
    if (!dir.exists()) {
        dir.mkdirs()
    }
    val file = File("$oldSdkPathDir$displayName")
    if (!file.exists()) {
        file.createNewFile()
    }
    return file.absolutePath
}


class DownloadToOutputStream(
        val scope: CoroutineScope,
        val url: String,
        val outputStream: OutputStream
) {
    private var downloadCall: Call? = null
    private var progressListener: (CoroutineScope.(current: Long, total: Long, progress: Float) -> Unit)? =
            null
    private var completeListener: (CoroutineScope.() -> Unit)? =
            null
    private var errorListener: ((error: Throwable) -> Unit)? = null

    fun start() {
        if (isMainThread()) {
            scope.launchOnThread {
                startDownload(this)
            }
        } else {
            startDownload(scope)
        }
    }

    private fun startDownload(coroutineScope: CoroutineScope) {
        var byteStream: InputStream? = null
        try {
            val client = OkHttpCertUtils.createCertOkHttpBuilder()
                    .readTimeout(5, TimeUnit.MINUTES)
                    .writeTimeout(5, TimeUnit.MINUTES)
                    .connectTimeout(5, TimeUnit.MINUTES)
                    .addNetworkInterceptor { chain ->
                        val originalResponse = chain.proceed(chain.request())
                        val oldBody = originalResponse.body
                        val newBody = if (oldBody != null) ProgressResponseBody(oldBody) {
                            onError(it)
                        } else null
                        originalResponse.newBuilder()
                                .body(newBody)
                                .build()
                    }
                    .build()
            val downRequest = Request.Builder()
                    .url(url)
                    .build()
            downloadCall = client.newCall(downRequest)
            val response = downloadCall?.execute()
            if (response?.code != 200) {
                onError(Exception("httpError:code:${response?.code},message:${response?.message}"))
                return
            }
            val body = response.body
            byteStream = body?.byteStream() ?: return
            val total = body.contentLength()

            var len: Int
            val buf = ByteArray(1024)
            var downloadSize = 0L
            while ((byteStream.read(buf).apply { len = this } > 0) && coroutineScope.isActive) {
                outputStream.write(buf, 0, len)
                downloadSize += len
                progressListener?.invoke(
                        coroutineScope,
                        downloadSize,
                        total,
                        downloadSize * 100f / total
                )
            }
            if (coroutineScope.isActive) {
                progressListener?.invoke(coroutineScope, downloadSize, total, 100f)
                completeListener?.invoke(coroutineScope)
            }
        } catch (e: Throwable) {
            onError(e)
        } finally {
            byteStream?.close()
            outputStream.flush()
            outputStream.close()
            logI("关闭输出流")
        }
    }

    private fun onError(e: Throwable) {
        e.printStackTrace()
        errorListener?.let {
            mainHandler.post {
                it.invoke(e)
            }
        }
    }

    fun cancel() {
        downloadCall?.cancel()
        scope.cancel()
    }

    fun setOnErrorListener(listener: ((error: Throwable) -> Unit)?) {
        errorListener = listener
    }

    fun setOnProgressListener(listener: (CoroutineScope.(current: Long, total: Long, progress: Float) -> Unit)?) {
        progressListener = listener
    }

    /**
     * 下载完成监听
     */
    fun setOnCompleteListener(listener: (CoroutineScope.() -> Unit)?) {
        completeListener = listener
    }
}